#ifndef BACKPACK_ACTION_H_
#define BACKPACK_ACTION_H_
#pragma once

#include "ActionVariant.hpp"
#include "UserActivity.hpp"
#include "GameContext.hpp"
#include "HotSpotVariant.hpp"
#include "Item.hpp"

#include <string>
#include <list>
#include <memory>

namespace backpack {


/**
 * Actions may be passed by value, so they should be light-weight
 */



class MessageAction {
public:
  MessageAction(std::string const& message)
    : m_strMessage(message) {
  }
  template <class View>
  void perform(View &view, GameContext &context) const {
    view.showMessage(m_strMessage);
  }

  bool operator==(MessageAction const& rhs) const {
    return rhs.m_strMessage == m_strMessage;
  }
private:
  std::string m_strMessage;
}; // class MessageAction




class MoveAction {
public:
  MoveAction(GameNode const& target)
    : m_rTargetNode(target) {
  }
  template <class View>
  void perform(View &view, GameContext &context) const {
    context.setCurrentNode(m_rTargetNode.get());
  }

  bool operator==(MoveAction const& rhs) const {
    return rhs.m_rTargetNode == m_rTargetNode;
  }
private:
  std::reference_wrapper<const GameNode> m_rTargetNode;
}; // class MoveAction




class AddItemAction {
public:
  AddItemAction(ItemVariant const& item)
    : m_rItemToAdd(item) {
  }
  template <class View>
  void perform(View &view, GameContext &context) const {
    view.showNewItem(m_rItemToAdd);
    context.addToInventory(m_rItemToAdd);
  }

  bool operator==(AddItemAction const& rhs) const {
    return rhs.m_rItemToAdd == m_rItemToAdd;
  }
private:
  ItemVariant m_rItemToAdd;
}; // class AddItemAction




template <class Derived>
class SingleContextAction {
public:
  template <class View>
  void perform(View &view, GameContext &context);

protected:
  SingleContextAction(std::string const& successMsg, std::string const& failMsg)
    : m_strSuccessMessage(successMsg), m_strFailMessage(failMsg) {
  }

private:
  std::string m_strSuccessMessage;
  std::string m_strFailMessage;
}; // class Action




template <class Derived>
class HotSpotAction :
  public SingleContextAction<Derived>,
  public boost::static_visitor<bool> {
public:
  typedef SingleContextAction<Derived> Base;
  HotSpotAction(std::string const& successMsg, std::string const& failMsg,
    HotSpotVariant const& hotspot)
    : Base(successMsg, failMsg), m_rHotSpot(hotspot) {
  }

  bool performImpl() {
    return boost::apply_visitor(
        makeHotSpotVisitor<bool>(*static_cast<Derived *>(this)),
        m_rHotSpot
        );
  }

  bool operator==(HotSpotAction<Derived> const& rhs) const {
    return &rhs.m_rHotSpot == &m_rHotSpot;
  }
private:
  HotSpotVariant m_rHotSpot;
}; // class HotSpotAction




class EnableAction :
  public HotSpotAction<EnableAction> {
public:
  typedef HotSpotAction<EnableAction> Base;
  EnableAction(std::string const& successMsg, std::string const& failMsg,
    HotSpotVariant const& hotspot)
    : Base(successMsg, failMsg, hotspot) {
  }
  template <class HotSpot>
  bool operator()(HotSpot &hotspot) {
    if (hotspot.isEnabled()) {
      return false;
    }
    hotspot.setEnabled(true);
    return true;
  }
}; // class EnableAction




class DisableAction :
  public HotSpotAction<DisableAction> {
public:
  typedef HotSpotAction<DisableAction> Base;
  DisableAction(std::string const& successMsg, std::string const& failMsg,
    HotSpotVariant const& hotspot)
    : Base(successMsg, failMsg, hotspot) {
  }
  template <class HotSpot>
  bool operator()(HotSpot &hotspot) {
    if (!hotspot.isEnabled()) {
      return false;
    }
    hotspot.setEnabled(false);
    return true;
  }
}; // class DisableAction




class EnabledSwitchAction :
  public HotSpotAction<EnabledSwitchAction> {
public:
  typedef HotSpotAction<EnabledSwitchAction> Base;
  EnabledSwitchAction(std::string const& successMsg, std::string const& failMsg,
    HotSpotVariant const& hotspot)
    : Base(successMsg, failMsg, hotspot) {
  }
  template <class HotSpot>
  bool operator()(HotSpot &hotspot) {
    hotspot.setEnabled(!hotspot.isEnabled());
    return true;
  }
}; // class EnabledSwitchAction




class ShowAction :
  public HotSpotAction<ShowAction> {
public:
  typedef HotSpotAction<ShowAction> Base;
  ShowAction(std::string const& successMsg, std::string const& failMsg,
    HotSpotVariant const& hotspot)
    : Base(successMsg, failMsg, hotspot) {
  }

  template <class HotSpot>
  bool operator()(HotSpot &hotspot) {
    if (hotspot.isVisible()) {
      return false;
    }
    hotspot.setVisible(true);
    return true;
  }
}; // class ShowAction




class HideAction :
  public HotSpotAction<HideAction> {
public:
  typedef HotSpotAction<HideAction> Base;
  HideAction(std::string const& successMsg, std::string const& failMsg,
    HotSpotVariant const& hotspot)
    : Base(successMsg, failMsg, hotspot) {
  }

  template <class HotSpot>
  bool operator()(HotSpot &hotspot) {
    if (!hotspot.isVisible()) {
      return false;
    }
    hotspot.setVisible(false);
    return true;
  }
}; // class HideAction




class VisibleSwitchAction :
  public HotSpotAction<VisibleSwitchAction> {
public:
  typedef HotSpotAction<VisibleSwitchAction> Base;
  VisibleSwitchAction(std::string const& successMsg, std::string const& failMsg,
    HotSpotVariant const& hotspot)
    : Base(successMsg, failMsg, hotspot) {
  }

  template <class HotSpot>
  bool operator()(HotSpot &hotspot) {
    hotspot.setVisible(!hotspot.isVisible());
    return true;
  }
}; // class VisibleSwitchAction



class LambdaAction {
public:
  LambdaAction(std::function<void(GameContext&)> func)
    : m_function(func) {
  }

  template <class View>
  void perform(View &, GameContext &context) {
    m_function(context);
  }

  bool operator==(LambdaAction const& rhs) const {
    return &rhs.m_function == &m_function;
  }
private:
  std::function<void(GameContext&)> m_function;
}; // class LambdaAction




class UserActivityAction {
public:
  UserActivityAction(UserActivityVariant const& activity)
    : m_activity(activity) {
  }

  template <class View>
  void perform(View &, GameContext &context) const {
    auto visitor = LetInvoker(context);
    m_activity.apply_visitor(visitor);
  }

  bool operator==(UserActivityAction const& rhs) const {
    return &rhs.m_activity == &m_activity;
  }
private:
  UserActivityVariant m_activity;

  class LetInvoker : public boost::static_visitor<> {
  public:
    LetInvoker(ActivityContext &context)
      : m_rContext(context) {
    }
    template <class Activity>
    void operator() (Activity const& activity) const {
      activity.let(m_rContext);
    }
  private:
    ActivityContext &m_rContext;
  }; // struct LetInvoker
}; // class LambdaAction




class MultiAction : public boost::static_visitor<> {
public:
  MultiAction()
    : m_listActions(new std::list<ActionVariant>) {
  }

  MultiAction& add(ActionVariant const& action) {
    m_listActions->push_back(action);
    return *this;
  }
  void clear() {
    m_listActions->clear();
  }

  template <class ActionVisitor>
  void accept(ActionVisitor & visitor) {
    std::for_each(
      m_listActions->begin(),
      m_listActions->end(),
      boost::apply_visitor(visitor)
      );
  }

  bool operator==(MultiAction const& rhs) const {
    return rhs.m_listActions == m_listActions;
  }
private:
  std::shared_ptr<std::list<ActionVariant>> m_listActions;
}; // class MultiAction




struct EndAction {
  bool operator==(EndAction const&) const {
    return true;
  }
}; // struct EndAction






template <class Derived>
template <class View>
void SingleContextAction<Derived>::perform(View &view, GameContext &context) {
  bool result = static_cast<Derived *>(this)->performImpl();
  std::string message = result? m_strSuccessMessage: m_strFailMessage;
  if (!message.empty()) {
    view.showMessage(message);
  }
}




} // namespace backpack

#endif // BACKPACK_ACTION_H_
